home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GEGA 010
/
GEGA010.iso
/
Mods
/
Doom 3
/
BloodyScreme
/
bloodymess.pk4
/
script
/
ai_monster_base.script
next >
Wrap
Text File
|
2004-08-04
|
45KB
|
2,089 lines
/***********************************************************************
ai_monster_base.script
base class for all monsters. implements common behavior.
***********************************************************************/
//
// attack flags
//
#define ATTACK_DODGE_LEFT 1
#define ATTACK_DODGE_RIGHT 2
#define ATTACK_COMBAT_NODE 4
#define ATTACK_MELEE 8
#define ATTACK_LEAP 16
#define ATTACK_MISSILE 32
#define ATTACK_SPECIAL1 64
#define ATTACK_SPECIAL2 128
#define ATTACK_SPECIAL3 256
#define ATTACK_SPECIAL4 512
#define AI_NOT_ACTIVATED 0
#define AI_CHASING_ENEMY 1
#define AI_LOST 2
#define AI_PATH_FOLLOWING 3
#define AI_ATTACK_NODE 4
/***********************************************************************
base class for monsters
***********************************************************************/
object monster_base : ai {
// common state variables
float run_distance; // distance to trigger running when chasing enemy
float walk_turn; // max turn amount allowed when running
boolean run;
boolean ambush;
boolean ignoreEnemies; // used to disable enemy checks during attack_path
boolean stay_on_attackpath; // used to disable enemy checks during attack_path
boolean idle_sight_fov;
float customBlendOut;
entity current_path;
entity next_path;
boolean resurrect;
boolean ignore_lostcombat;
boolean blocked;
boolean ignore_sight;
// common animation functions
void Torso_CustomCycle();
void Torso_CustomAnim();
void Torso_Sight();
void Torso_Death();
void Legs_Idle();
void Legs_Death();
// attack checks
// these functions are meant to be implemented by subclasses.
// by default, they do nothing.
float check_attacks();
void do_attack( float attack_flags );
boolean checkTurretAttack(); // tests if monster can still do a turret attack from current location
// common functions
void init();
void state_Begin();
void monster_begin();
void archvile_minion();
boolean can_resurrect();
void monster_resurrect( entity enemy );
void wait_for_enemy();
void state_Killed();
void state_Dead();
void wake_on_enemy();
void wake_on_trigger();
void walk_on_trigger();
void wake_on_attackcone();
void sight_enemy();
void enemy_dead();
void state_Combat();
void state_LostCombat();
void state_Spawner();
void monster_base_lost_combat();
boolean check_blocked();
boolean combat_chase();
void combat_turret_node( entity node );
void combat_attack_cone( entity node );
void combat_ainode( entity node );
void combat_lost();
void combat_wander();
void playCustomCycle( string animname, float blendTime );
void playCustomAnim( string animname, float blendIn, float blendOut );
void checkNodeAnim( entity node, string prefix, string anim );
void trigger_wakeup_targets();
boolean checkForEnemy( float use_fov );
void state_FollowAlternatePath();
void follow_alternate_path1();
void follow_alternate_path2();
void follow_alternate_path3();
// path following
void idle_followPathEntities( entity pathnode );
void path_corner();
void path_anim();
void path_cycleanim();
void path_turn();
void path_wait();
void path_waitfortrigger();
void path_hide();
void path_show();
void path_attack();
};
/***********************************************************************
Torso animation control
***********************************************************************/
void monster_base::Torso_CustomCycle() {
eachFrame {
;
}
}
void monster_base::Torso_CustomAnim() {
while( !animDone( ANIMCHANNEL_TORSO, customBlendOut ) ) {
waitFrame();
}
finishAction( "customAnim" );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", customBlendOut );
}
void monster_base::Torso_Sight() {
string animname;
float blendFrames;
if ( !getIntKey( "walk_on_sight" ) ) {
overrideAnim( ANIMCHANNEL_LEGS );
}
animname = self.getKey( "on_activate" );
if ( self.getKey( "on_activate_blend" ) ) {
blendFrames = self.getIntKey( "on_activate_blend" );
} else {
blendFrames = 4;
}
if ( animname != "" ) {
if ( hasAnim( ANIMCHANNEL_TORSO, animname ) ) {
playAnim( ANIMCHANNEL_TORSO, animname );
while( !animDone( ANIMCHANNEL_TORSO, blendFrames ) ) {
waitFrame();
}
}
}
finishAction( "sight" );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames );
}
void monster_base::Torso_Death() {
finishAction( "dead" );
}
void monster_base::Legs_Idle() {
// implemented by subclasses
}
void monster_base::Legs_Death() {
}
/***********************************************************************
AI
***********************************************************************/
/*
=====================
monster_base::init
=====================
*/
void monster_base::init() {
ignoreEnemies = false;
run_distance = 0;
walk_turn = 360;
ambush = getIntKey( "ambush" );
ignore_lostcombat = getIntKey( "ignore_lostcombat" );
stay_on_attackpath = getIntKey( "stay_on_attackpath" );
idle_sight_fov = true;
run = false;
}
/*
=====================
monster_base::state_Begin
Initial state for monsters. Called after spawn and when monster is resurrected.
=====================
*/
void monster_base::state_Begin() {
}
/*
=====================
monster_base::do_attack
Performs an attack based on which bits are set on the attack_flags.
Implemented by subclasses
=====================
*/
void monster_base::do_attack( float attack_flags ) {
}
/*
=====================
monster_base::check_attacks
Returns flags stating which attacks can be performed this frame.
Implemented by subclasses
=====================
*/
float monster_base::check_attacks() {
return 0;
}
/*
=====================
monster_base::playCustomCycle
=====================
*/
void monster_base::playCustomCycle( string animname, float blendTime ) {
animState( ANIMCHANNEL_TORSO, "Torso_CustomCycle", blendTime );
overrideAnim( ANIMCHANNEL_LEGS );
playCycle( ANIMCHANNEL_TORSO, animname );
}
/*
=====================
monster_base::playCustomAnim
=====================
*/
void monster_base::playCustomAnim( string animname, float blendIn, float blendOut ) {
customBlendOut = blendOut;
animState( ANIMCHANNEL_TORSO, "Torso_CustomAnim", blendIn );
overrideAnim( ANIMCHANNEL_LEGS );
playAnim( ANIMCHANNEL_TORSO, animname );
}
/*
=====================
monster_base::trigger_wakeup_targets
=====================
*/
void monster_base::trigger_wakeup_targets() {
string key;
string name;
entity ent;
key = getNextKey( "wakeup_target", "" );
while( key != "" ) {
name = getKey( key );
ent = sys.getEntity( name );
if ( !ent ) {
sys.warning( "Unknown wakeup_target '" + name + "' on entity '" + getName() + "'" );
} else {
sys.trigger( ent );
}
key = getNextKey( "wakeup_target", key );
}
}
/*
=====================
monster_base::checkForEnemy
=====================
*/
boolean monster_base::checkForEnemy( float use_fov ) {
entity enemy;
vector size;
float dist;
if ( sys.influenceActive() ) {
return false;
}
if ( AI_PAIN ) {
// get out of ambush mode when shot
ambush = false;
}
if ( ignoreEnemies ) {
// while we're following paths, we only respond to enemies on pain, or when close enough to them
if ( stay_on_attackpath ) {
// don't exit attack_path when close to enemy
return false;
}
enemy = getEnemy();
if ( !enemy ) {
enemy = findEnemy( false );
}
if ( !enemy ) {
return false;
}
size = getSize();
dist = ( size_x * 1.414 ) + 16; // diagonal distance plus 16 units
if ( enemyRange() > dist ) {
return false;
}
} else {
if ( getEnemy() ) {
// we were probably triggered (which sets our enemy)
return true;
}
if ( !ignore_sight ) {
enemy = findEnemy( use_fov );
}
if ( !enemy ) {
if ( ambush ) {
return false;
}
enemy = heardSound( true );
if ( !enemy ) {
return false;
}
}
}
ignoreEnemies = false;
// once we've woken up, get out of ambush mode
ambush = false;
// don't use the fov for sight anymore
idle_sight_fov = false;
setEnemy( enemy );
return true;
}
/*
=====================
monster_base::state_Spawner
=====================
*/
void monster_base::state_Spawner() {
entity ent;
float triggerCount;
float maxSpawn;
float i;
string name;
maxSpawn = getIntKey( "spawner" );
name = getName();
hide();
triggerCount = 0;
AI_ACTIVATED = false;
while( 1 ) {
if ( AI_ACTIVATED ) {
triggerCount++;
AI_ACTIVATED = false;
}
if ( triggerCount ) {
if ( canBecomeSolid() ) {
for( i = 0; i < maxSpawn; i++ ) {
ent = sys.getEntity( name + i );
if ( !ent ) {
break;
}
}
if ( i < maxSpawn ) {
triggerCount--;
sys.copySpawnArgs( self );
sys.setSpawnArg( "spawner", "0" );
sys.setSpawnArg( "name", name + i );
if ( getKey( "spawn_target" ) != "" ) {
sys.setSpawnArg( "target", getKey( "spawn_target" ) );
}
ent = sys.spawn( getKey( "classname" ) );
sys.trigger( ent );
}
}
}
waitFrame();
}
}
/*
=====================
monster_base::monster_begin
=====================
*/
void monster_base::monster_begin() {
float teleportType;
string triggerAnim;
float waittime;
boolean start_active;
entity path;
float movetype;
entity enemy;
run = false;
ignore_sight = getIntKey( "no_sight" );
if ( !getIntKey( "ignore_flashlight" ) ) {
// allow waking up from the flashlight
wakeOnFlashlight( true );
}
start_active = false;
if ( resurrect ) {
teleportType = 4;
triggerAnim = "";
AI_ACTIVATED = true;
} else {
teleportType = getIntKey( "teleport" );
triggerAnim = getKey( "trigger_anim" );
}
if ( getIntKey( "spawner" ) ) {
setState( "state_Spawner" );
}
if ( triggerAnim != "" ) {
//
// hide until triggered and then play a special animation
//
checkAnim( ANIMCHANNEL_TORSO, triggerAnim );
hide();
waitUntil( AI_ACTIVATED );
waitUntil( canBecomeSolid() );
// don't go dormant during trigger_anim anims since they
// may end up floating in air during no gravity anims.
setNeverDormant( true );
show();
trigger_wakeup_targets();
playCustomAnim( triggerAnim, 0, 4 );
waitAction( "customAnim" );
setNeverDormant( getFloatKey( "neverdormant" ) );
locateEnemy();
start_active = true;
} else if ( teleportType > 0 ) {
//
// teleport in when triggered
//
hide();
waitUntil( AI_ACTIVATED );
waitUntil( canBecomeSolid() );
becomeSolid();
movetype = getMoveType();
setMoveType( MOVETYPE_STATIC );
// don't go dormant during teleport anims since they
// may end up floating in air during no gravity anims.
setNeverDormant( true );
trigger_wakeup_targets();
if ( teleportType == 1 ) {
startFx( getKey( "fx_teleport1" ) );
wait( 1.6 );
} else if ( teleportType == 2 ) {
startFx( getKey( "fx_teleport2" ) );
wait( 2.6 );
} else if ( teleportType == 3 ) {
startFx( getKey( "fx_teleport3" ) );
wait( 3.6 );
} else {
startFx( getKey( "fx_teleport" ) );
wait( 0.6 );
}
show();
playCustomAnim( "teleport", 0, 4 );
waitAction( "customAnim" );
setNeverDormant( getFloatKey( "neverdormant" ) );
locateEnemy();
setMoveType( movetype );
start_active = true;
} else if ( getIntKey( "hide" ) ) {
//
// hide until triggered
//
hide();
waitUntil( AI_ACTIVATED );
if ( ( getIntKey( "hide" ) == 1 ) || ambush ) {
AI_ACTIVATED = false;
clearEnemy();
}
waitUntil( canBecomeSolid() );
show();
}
waittime = getFloatKey( "wait" );
if ( waittime > 0 ) {
sys.wait( waittime );
}
enemy = getEntityKey( "enemy" );
if ( enemy ) {
setEnemy( enemy );
}
if ( !start_active ) {
if ( getIntKey( "wake_on_attackcone" ) ) {
wake_on_attackcone();
} else if ( getIntKey( "walk_on_trigger" ) ) {
walk_on_trigger();
} else if ( getIntKey( "trigger" ) ) {
wake_on_trigger();
} else {
wake_on_enemy();
}
} else if ( getIntKey( "attack_path" ) ) {
// follow a path and fight player at end
path = randomPath();
if ( path ) {
run = true;
ignoreEnemies = true;
idle_followPathEntities( path );
ignoreEnemies = false;
}
}
// allow him to see after he's woken up
ignore_sight = false;
// ignore the flashlight from now on
wakeOnFlashlight( false );
}
/*
=====================
monster_base::archvile_minion
=====================
*/
void monster_base::archvile_minion() {
hide();
resurrect = true;
AI_DEAD = true;
}
/*
=====================
monster_base::can_resurrect
=====================
*/
boolean monster_base::can_resurrect() {
if ( !AI_DEAD ) {
return false;
}
if ( !isHidden() ) {
return false;
}
if ( !canBecomeSolid() ) {
return false;
}
return true;
}
/*
=====================
monster_base::state_Resurrect
=====================
*/
void monster_base::state_Resurrect() {
float health;
vector ang;
AI_DEAD = false;
stopMove();
hide();
stopRagdoll();
restorePosition();
waitUntil( canBecomeSolid() );
health = getFloatKey( "health" );
setHealth( health );
ang_y = getFloatKey( "angle" );
setAngles( ang );
turnTo( ang_y );
clearBurn();
allowDamage();
setState( "state_Begin" );
}
/*
=====================
monster_base::monster_resurrect
=====================
*/
void monster_base::monster_resurrect( entity enemy ) {
// mark them as not dead so we don't get resurrected twice this frame
AI_DEAD = false;
if ( enemy ) {
setEnemy( enemy );
} else {
setEnemy( $player1 );
}
setState( "state_Resurrect" );
}
/*
=====================
monster_base::wait_for_enemy
=====================
*/
void monster_base::wait_for_enemy() {
// prevent an infinite loop when in notarget
AI_PAIN = false;
stopMove();
while( !AI_PAIN && !getEnemy() ) {
if ( checkForEnemy( idle_sight_fov ) ) {
break;
}
waitFrame();
}
}
/*
=====================
monster_base::state_Killed
=====================
*/
void monster_base::state_Killed() {
stopMove();
animState( ANIMCHANNEL_TORSO, "Torso_Death", 0 );
animState( ANIMCHANNEL_LEGS, "Legs_Death", 0 );
waitAction( "dead" );
setState( "state_Dead" );
}
/*
=====================
monster_base::state_Dead
=====================
*/
void monster_base::state_Dead() {
// float burnDelay = getFloatKey( "burnaway" );
// if ( burnDelay != 0 ) {
// preBurn();
// sys.wait( burnDelay );
// burn();
// startSound( "snd_burn", SND_CHANNEL_BODY, false );
// }
sys.wait( 3 );
if ( resurrect ) {
hide();
restorePosition();
// wait until we're resurrected
waitUntil( 0 );
}
// remove();
}
/*
=====================
monster_base::sight_enemy
=====================
*/
void monster_base::sight_enemy() {
string animname;
faceEnemy();
animname = self.getKey( "on_activate" );
if ( animname != "" ) {
// don't go dormant during on_activate anims since they
// may end up floating in air during no gravity anims.
setNeverDormant( true );
if ( getIntKey( "walk_on_sight" ) ) {
moveToEnemy();
}
animState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
waitAction( "sight" );
setNeverDormant( getFloatKey( "neverdormant" ) );
}
}
/*
=====================
monster_base::enemy_dead
=====================
*/
void monster_base::enemy_dead() {
AI_ENEMY_DEAD = false;
checkForEnemy( false );
if ( !getEnemy() ) {
waitFrame(); // avoid infinite loops
setState( "state_Idle" );
} else {
setState( "state_Combat" );
}
}
/*
=====================
monster_base::walk_on_trigger
=====================
*/
void monster_base::walk_on_trigger() {
string animname;
entity path;
allowMovement( false );
// sit in our idle anim till we're activated
animname = self.getKey( "anim" );
playCustomCycle( animname, 4 );
waitUntil( AI_ACTIVATED || AI_PAIN );
if ( AI_ACTIVATED ) {
clearEnemy();
AI_ACTIVATED = false;
}
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
allowMovement( true );
// follow a path
path = randomPath();
if ( path ) {
idle_followPathEntities( path );
}
if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) {
// sit in our idle anim till we're activated
allowMovement( false );
playCustomCycle( animname, 4 );
while( !AI_PAIN && !AI_ACTIVATED ) {
if ( checkForEnemy( true ) ) {
break;
}
waitFrame();
}
allowMovement( true );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
}
trigger_wakeup_targets();
sight_enemy();
}
/*
=====================
monster_base::wake_on_trigger
=====================
*/
void monster_base::wake_on_trigger() {
string animname;
entity path;
if ( !getIntKey( "attack_path" ) ) {
path = randomPath();
if ( path ) {
idle_followPathEntities( path );
}
}
if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) {
// sit in our idle anim till we're activated
allowMovement( false );
animname = self.getKey( "anim" );
playCustomCycle( animname, 4 );
waitUntil( AI_ACTIVATED || AI_PAIN );
allowMovement( true );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
}
trigger_wakeup_targets();
if ( getIntKey( "attack_path" ) ) {
// follow a path and fight player at end
path = randomPath();
if ( path ) {
ignoreEnemies = true;
run = true;
idle_followPathEntities( path );
ignoreEnemies = false;
}
} else {
sight_enemy();
}
}
/*
=====================
monster_base::wake_on_enemy
=====================
*/
void monster_base::wake_on_enemy() {
string animname;
entity path;
if ( !getIntKey( "attack_path" ) ) {
path = randomPath();
if ( path ) {
idle_followPathEntities( path );
}
}
if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) {
// sit in our idle anim till we're activated
allowMovement( false );
animname = self.getKey( "anim" );
playCustomCycle( animname, 4 );
while( !AI_PAIN && !AI_ACTIVATED ) {
if ( checkForEnemy( true ) ) {
break;
}
waitFrame();
}
}
allowMovement( true );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
if ( getIntKey( "attack_path" ) ) {
// follow a path and fight player at end
path = randomPath();
if ( path ) {
run = true;
ignoreEnemies = true;
idle_followPathEntities( path );
ignoreEnemies = false;
}
} else {
sight_enemy();
}
}
/*
=====================
monster_base::wake_on_attackcone
=====================
*/
void monster_base::wake_on_attackcone() {
string animname;
entity path;
entity enemy;
if ( !getIntKey( "attack_path" ) ) {
path = randomPath();
if ( path ) {
idle_followPathEntities( path );
run = path.getIntKey( "run" );
while( !AI_MOVE_DONE && !AI_ACTIVATED && !AI_PAIN ) {
enemy = findEnemyInCombatNodes();
if ( enemy ) {
setEnemy( enemy );
break;
}
waitFrame();
}
}
}
if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) {
// sit in our idle anim till we're activated
allowMovement( false );
animname = self.getKey( "anim" );
playCustomCycle( animname, 4 );
while( !AI_ACTIVATED && !AI_PAIN ) {
enemy = findEnemyInCombatNodes();
if ( enemy ) {
setEnemy( enemy );
break;
}
waitFrame();
}
allowMovement( true );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
}
trigger_wakeup_targets();
if ( getIntKey( "attack_path" ) ) {
// follow a path and fight player at end
path = randomPath();
if ( path ) {
run = true;
ignoreEnemies = true;
idle_followPathEntities( path );
ignoreEnemies = false;
}
} else {
sight_enemy();
}
}
/*
=====================
monster_base::idle_followPathEntities
=====================
*/
void monster_base::idle_followPathEntities( entity pathnode ) {
string nodeaction;
string triggername;
entity triggerent;
current_path = pathnode;
do {
next_path = current_path.randomPath();
nodeaction = current_path.getKey( "classname" );
if ( hasFunction( nodeaction ) ) {
callFunction( nodeaction );
} else {
sys.warning( "'" + getName() + "' encountered an unsupported path entity '" + nodeaction + "' on entity '" + current_path.getName() + "'\n" );
return;
}
if ( checkForEnemy( true ) ) {
return;
}
// trigger any entities the path had targeted
triggername = current_path.getKey( "trigger" );
if ( triggername != "" ) {
triggerent = sys.getEntity( triggername );
if ( triggerent ) {
triggerent.activate( self );
}
}
current_path = next_path;
} while( !( !current_path ) );
}
/***********************************************************************
path functions
***********************************************************************/
/*
=====================
monster_base::path_corner
=====================
*/
void monster_base::path_corner() {
string customAnim;
if ( current_path.getKey( "run" ) != "" ) {
run = current_path.getIntKey( "run" );
}
customAnim = current_path.getKey( "anim" );
while( 1 ) {
if ( customAnim != "" ) {
playCustomCycle( customAnim, 4 );
}
moveToEntity( current_path );
waitFrame();
while( !AI_MOVE_DONE ) {
if ( sys.influenceActive() ) {
break;
}
if ( checkForEnemy( true ) ) {
break;
}
waitFrame();
}
if ( customAnim != "" ) {
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
}
if ( sys.influenceActive() ) {
stopMove();
while( sys.influenceActive() ) {
waitFrame();
}
continue;
}
break;
}
if ( AI_DEST_UNREACHABLE ) {
// Can't reach
sys.warning( "entity '" + getName() + "' couldn't reach path_corner '" + current_path.getName() + "'" );
waitFrame();
}
}
/*
=====================
monster_base::path_anim
=====================
*/
void monster_base::path_anim() {
string animname;
float ang;
float blend_in;
float blend_out;
animname = current_path.getKey( "anim" );
blend_in = current_path.getIntKey( "blend_in" );
blend_out = current_path.getIntKey( "blend_out" );
if ( current_path.getKey( "angle" ) != "" ) {
ang = current_path.getFloatKey( "angle" );
turnTo( ang );
while( !facingIdeal() ) {
if ( checkForEnemy( true ) ) {
return;
}
waitFrame();
}
}
playCustomAnim( animname, blend_in, blend_out );
while( !animDone( ANIMCHANNEL_TORSO, blend_out ) ) {
if ( checkForEnemy( true ) ) {
break;
}
waitFrame();
}
animState( ANIMCHANNEL_TORSO, "Torso_Idle", blend_out );
}
/*
=====================
monster_base::path_cycleanim
=====================
*/
void monster_base::path_cycleanim() {
string animname;
vector ang;
float blend_in;
float blend_out;
float waittime;
animname = current_path.getKey( "anim" );
blend_in = current_path.getIntKey( "blend_in" );
blend_out = current_path.getIntKey( "blend_out" );
ang = current_path.getAngles();
turnTo( ang_y );
while( !facingIdeal() ) {
if ( checkForEnemy( true ) ) {
return;
}
waitFrame();
}
playCustomCycle( animname, blend_in );
waittime = current_path.getFloatKey( "wait" );
if ( waittime ) {
waittime += sys.getTime();
while( sys.getTime() < waittime ) {
if ( checkForEnemy( true ) ) {
return;
}
waitFrame();
}
} else {
AI_ACTIVATED = false;
while( !AI_ACTIVATED ) {
if ( checkForEnemy( true ) ) {
return;
}
waitFrame();
}
}
animState( ANIMCHANNEL_TORSO, "Torso_Idle", blend_out );
}
/*
=====================
monster_base::path_turn
=====================
*/
void monster_base::path_turn() {
vector ang;
ang = current_path.getAngles();
turnTo( ang_y );
while( !facingIdeal() ) {
if ( checkForEnemy( true ) ) {
return;
}
waitFrame();
}
}
/*
=====================
monster_base::path_wait
=====================
*/
void monster_base::path_wait() {
float waittime;
waittime = current_path.getFloatKey( "wait" );
waittime += sys.getTime();
while( sys.getTime() < waittime ) {
if ( checkForEnemy( true ) ) {
return;
}
waitFrame();
}
}
/*
=====================
monster_base::path_waitfortrigger
=====================
*/
void monster_base::path_waitfortrigger() {
AI_ACTIVATED = false;
while( !AI_ACTIVATED ) {
if ( checkForEnemy( true ) ) {
return;
}
waitFrame();
}
AI_ACTIVATED = false;
}
/*
=====================
monster_base::path_hide
=====================
*/
void monster_base::path_hide() {
hide();
}
/*
=====================
monster_base::path_show
=====================
*/
void monster_base::path_show() {
waitUntil( canBecomeSolid() );
show();
}
/*
=====================
monster_base::path_attack
=====================
*/
void monster_base::path_attack() {
entity enemy;
float delta;
boolean do_run;
float range;
float attack_flags;
enemy = current_path.getEntityKey( "enemy" );
if ( !enemy ) {
return;
}
setEnemy( enemy );
locateEnemy();
AI_ACTIVATED = false;
while( !AI_ACTIVATED && !( !enemy ) ) {
stopMove();
while( sys.influenceActive() ) {
waitFrame();
}
if ( enemyRange() > run_distance ) {
do_run = true;
} else {
do_run = false;
}
// set our enemy again in case we were shot by the player
setEnemy( enemy );
if ( AI_ENEMY_DEAD ) {
break;
}
moveToEnemy();
if ( AI_MOVE_DONE ) {
locateEnemy();
moveToEnemy();
if ( AI_MOVE_DONE ) {
// prevent runaway loops if monster can't reach enemy
waitFrame();
}
}
while( !AI_ACTIVATED && !AI_MOVE_DONE && !AI_DEST_UNREACHABLE && !( !enemy ) ) {
// set our enemy again in case we were shot by the player
setEnemy( enemy );
if ( AI_ENEMY_DEAD ) {
break;
}
moveToEnemy();
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
if ( sys.influenceActive() ) {
break;
}
attack_flags = check_attacks();
if ( attack_flags ) {
do_attack( attack_flags );
}
range = enemyRange();
if ( range > run_distance ) {
do_run = true;
}
delta = getTurnDelta();
if ( ( delta > walk_turn ) || ( delta < -walk_turn ) ) {
run = false;
} else {
run = do_run;
}
waitFrame();
}
stopMove();
}
}
/***********************************************************************
Combat
***********************************************************************/
/*
=====================
monster_base::state_Combat
=====================
*/
void monster_base::state_Combat() {
float attack_flags;
eachFrame {
faceEnemy();
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
if ( sys.influenceActive() ) {
waitFrame();
continue;
}
if ( AI_ENEMY_DEAD ) {
enemy_dead();
}
attack_flags = check_attacks();
if ( attack_flags ) {
do_attack( attack_flags );
continue;
}
if ( !combat_chase() ) {
locateEnemy();
if ( !combat_chase() ) {
combat_lost();
}
}
waitFrame();
}
}
/*
=====================
monster_base::check_blocked
returns true when an attack was called, since the move command may be different from when entering the function.
=====================
*/
boolean monster_base::check_blocked() {
entity obstacle;
float attack_flags;
monster_base monster;
float endTime;
boolean oldrun;
oldrun = run;
if ( AI_BLOCKED ) {
//sys.print( sys.getTime() + " : " + getName() + " is stuck in place\n" );
saveMove();
run = true;
wander();
endTime = sys.getTime() + 2;
while( sys.getTime() < endTime ) {
attack_flags = check_attacks();
if ( attack_flags ) {
restoreMove();
do_attack( attack_flags );
run = oldrun;
return true;
}
waitFrame();
}
restoreMove();
} else if ( moveStatus() == MOVE_STATUS_BLOCKED_BY_OBJECT ) {
float force = getFloatKey( "kick_force" );
if ( !force ) {
force = 60;
}
kickObstacles( getObstacle(), force );
} else if ( moveStatus() > MOVE_STATUS_BLOCKED_BY_OBJECT ) {
// just wait for the path to be clear
obstacle = getObstacle();
monster = obstacle;
if ( monster ) {
if ( monster.blocked ) {
run = oldrun;
return false;
}
}
blocked = true;
saveMove();
while( moveStatus() > MOVE_STATUS_BLOCKED_BY_OBJECT ) {
run = true;
wander();
endTime = sys.getTime() + 1;
while( sys.getTime() < endTime ) {
if ( AI_MOVE_DONE ) {
faceEnemy();
}
attack_flags = check_attacks();
if ( attack_flags ) {
blocked = false;
restoreMove();
do_attack( attack_flags );
run = oldrun;
return true;
}
waitFrame();
}
restoreMove();
}
blocked = false;
}
run = oldrun;
return false;
}
/*
=====================
monster_base::combat_chase
=====================
*/
boolean monster_base::combat_chase() {
float delta;
boolean do_run;
float range;
float attack_flags;
if ( !AI_ENEMY_VISIBLE || ( enemyRange() > run_distance ) ) {
do_run = true;
} else {
do_run = false;
}
moveToEnemy();
if ( AI_MOVE_DONE ) {
return false;
}
waitFrame();
if ( AI_MOVE_DONE ) {
attack_flags = check_attacks();
if ( attack_flags ) {
do_attack( attack_flags );
return true;
}
return false;
}
while( !AI_MOVE_DONE && !AI_DEST_UNREACHABLE ) {
if ( AI_ENEMY_DEAD ) {
enemy_dead();
}
if ( sys.influenceActive() ) {
return true;
}
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
attack_flags = check_attacks();
if ( attack_flags ) {
do_attack( attack_flags );
return true;
}
if ( check_blocked() ) {
return true;
}
range = enemyRange();
if ( !AI_ENEMY_VISIBLE || ( range > run_distance ) ) {
do_run = true;
}
delta = getTurnDelta();
if ( ( delta > walk_turn ) || ( delta < -walk_turn ) ) {
run = false;
} else {
run = do_run;
}
waitFrame();
}
return true;
}
/*
=====================
monster_base::checkTurretAttack
tests if monster can still do a turret attack from current location
=====================
*/
boolean monster_base::checkTurretAttack() {
return canHitEnemy();
}
/*
=====================
monster_base::combat_turret_node
=====================
*/
void monster_base::combat_turret_node( entity node ) {
vector pos;
float min_wait;
float max_wait;
float num_shots;
float wait_end;
float current_time;
float count;
boolean exit;
boolean do_melee;
min_wait = node.getFloatKey( "min_wait" );
max_wait = node.getFloatKey( "max_wait" );
num_shots = node.getFloatKey( "num_shots" );
pos = node.getOrigin();
exit = false;
while( !AI_ENEMY_DEAD ) {
if ( !node ) {
// level designers sometimes remove the combat node, so check for it so the thread doesn't get killed
break;
}
// run to the combat node
allowMovement( true );
run = true;
moveToEntity( node );
while( !AI_MOVE_DONE ) {
if ( sys.influenceActive() ) {
stopMove();
while( sys.influenceActive() ) {
waitFrame();
}
moveToEntity( node );
}
if ( !node ) {
// level designers sometimes remove the combat node, so check for it so the thread doesn't get killed
break;
}
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
// exit out if we can't get there or we can do a melee attack
do_melee = testMeleeAttack();
if ( AI_DEST_UNREACHABLE || do_melee ) {
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
return;
}
waitFrame();
}
if ( !AI_ENEMY_VISIBLE ) {
waitFrame();
continue;
}
if ( !checkTurretAttack() ) {
waitFrame();
continue;
}
if ( !node ) {
// level designers sometimes remove the combat node, so check for it so the thread doesn't get killed
break;
}
faceEnemy();
allowMovement( false );
// make sure he's precisely at the node
slideTo( pos, 0.25 );
waitUntil( AI_MOVE_DONE );
faceEnemy();
// do our attack
allowMovement( false );
count = int( sys.random( num_shots ) ) + 1;
while( count > 0 ) {
if ( sys.influenceActive() ) {
break;
}
if ( !node ) {
// level designers sometimes remove the combat node, so check for it so the thread doesn't get killed
exit = true;
break;
}
animState( ANIMCHANNEL_TORSO, "Torso_TurretAttack", 4 );
while( inAnimState( ANIMCHANNEL_TORSO, "Torso_TurretAttack" ) ) {
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
waitFrame();
}
if ( testMeleeAttack() || AI_ENEMY_DEAD ) {
exit = true;
break;
}
count--;
}
if ( exit ) {
break;
}
faceEnemy();
current_time = sys.getTime();
wait_end = current_time + min_wait + sys.random( max_wait - min_wait );
while( sys.influenceActive() || ( sys.getTime() < wait_end ) ) {
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
// exit out if we can do a melee attack or enemy is dead
// level designers sometimes remove the combat node, so check for it so the thread doesn't get killed
if ( testMeleeAttack() || AI_ENEMY_DEAD || !node ) {
exit = true;
break;
}
waitFrame();
}
if ( exit ) {
break;
}
}
if ( node ) {
// we're done with the node, so get rid of it so we don't use it again
node.remove();
}
allowMovement( true );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
}
/*
=====================
monster_base::checkNodeAnim
=====================
*/
void monster_base::checkNodeAnim( entity node, string prefix, string anim ) {
if ( !hasAnim( ANIMCHANNEL_TORSO, prefix + "_" + anim ) ) {
sys.error( "AI node '" + node.getName() + "' specifies missing anim '" + prefix + "_" + anim + "' on monster '" + getName() + "'" );
}
}
/*
=====================
monster_base::combat_attack_cone
=====================
*/
void monster_base::combat_attack_cone( entity node ) {
vector ang;
vector pos;
float min_wait;
float max_wait;
float num_shots;
float wait_end;
float current_time;
float count;
boolean exit;
boolean attack;
boolean do_melee;
boolean dont_wait;
string prefix;
// run to the combat node
run = true;
moveToEntity( node );
while( !AI_MOVE_DONE ) {
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
// exit out if we can't get there or we can do a melee attack
do_melee = testMeleeAttack();
if ( AI_DEST_UNREACHABLE || do_melee || !enemyInCombatCone( node, false ) || AI_ENEMY_DEAD || !node ) {
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
return;
}
waitFrame();
}
if ( !node ) {
// level designers sometimes remove the combat node, so check for it so the thread doesn't get killed
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
return;
}
// set our anim prefix
prefix = node.getKey( "anim" );
if ( prefix == "" ) {
sys.error( "Missing 'anim' key on entity '" + node.getName() + "'" );
}
setAnimPrefix( prefix );
checkNodeAnim( node, prefix, "out" );
checkNodeAnim( node, prefix, "fire" );
checkNodeAnim( node, prefix, "in" );
checkNodeAnim( node, prefix, "wait" );
min_wait = node.getFloatKey( "min_wait" );
max_wait = node.getFloatKey( "max_wait" );
num_shots = node.getFloatKey( "num_shots" );
dont_wait = getIntKey( "wake_on_attackcone" );
if ( dont_wait ) {
setKey( "wake_on_attackcone", "0" );
}
// face the direction the node points
ang = node.getAngles();
turnTo( ang_y );
exit = false;
attack = false;
pos = node.getOrigin();
while( !( !node ) && enemyInCombatCone( node, true ) ) {
allowMovement( false );
// play the wait animation
playCustomCycle( "wait", 4 );
// make sure he's precisely at the node
slideTo( pos, 0.5 );
current_time = sys.getTime();
if ( dont_wait ) {
dont_wait = false;
wait_end = current_time + 0.5;
} else {
wait_end = current_time + min_wait + sys.random( max_wait - min_wait );
}
while( sys.influenceActive() || !AI_MOVE_DONE || !facingIdeal() || ( current_time < wait_end ) ) {
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
// exit out if we can do a melee attack or enemy has left the combat cone
if ( testMeleeAttack() ) {
exit = true;
break;
}
if ( !node || !enemyInCombatCone( node, false ) ) {
exit = true;
break;
}
waitFrame();
current_time = sys.getTime();
}
if ( exit ) {
break;
}
// do our attack
allowMovement( true );
playCustomAnim( "out", 4, 0 );
while( !animDone( ANIMCHANNEL_TORSO, 0 ) ) {
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
if ( testMeleeAttack() ) {
exit = true;
break;
}
if ( !node || !enemyInCombatCone( node, true ) ) {
exit = true;
break;
}
waitFrame();
}
if ( exit ) {
break;
}
AI_DAMAGE = false;
allowMovement( false );
count = int( sys.random( num_shots ) ) + 1;
while( !AI_DAMAGE && ( count > 0 ) ) {
if ( sys.influenceActive() ) {
break;
}
playCustomAnim( "fire", 0, 0 );
while( !animDone( ANIMCHANNEL_TORSO, 0 ) ) {
if ( sys.influenceActive() ) {
break;
}
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
if ( testMeleeAttack() ) {
exit = true;
break;
}
if ( !node || !enemyInCombatCone( node, true ) ) {
exit = true;
break;
}
waitFrame();
}
attack = true;
if ( testMeleeAttack() ) {
exit = true;
break;
}
if ( !node || !enemyInCombatCone( node, true ) ) {
exit = true;
break;
}
count--;
}
AI_DAMAGE = false;
if ( exit ) {
break;
}
allowMovement( true );
playCustomAnim( "in", 0, 4 );
while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) {
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
if ( testMeleeAttack() ) {
exit = true;
break;
}
if ( !node || !enemyInCombatCone( node, true ) ) {
exit = true;
break;
}
waitFrame();
}
if ( exit ) {
break;
}
}
if ( !( !node ) && attack ) {
// mark the node as used in case it's single use
node.markUsed();
}
allowMovement( true );
setAnimPrefix( "" );
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
}
/*
=====================
monster_base::combat_ainode
=====================
*/
void monster_base::combat_ainode( entity node ) {
if ( node.getKey( "classname" ) == "ai_attackcone_turret" ) {
combat_turret_node( node );
} else {
combat_attack_cone( node );
}
}
/*
=====================
monster_base::combat_lost
=====================
*/
void monster_base::combat_lost() {
if ( !ignore_lostcombat ) {
setState( "state_LostCombat" );
}
}
/*
=====================
monster_base::state_LostCombat
=====================
*/
void monster_base::state_LostCombat() {
monster_base_lost_combat();
}
/*
=====================
monster_base::monster_base_lost_combat
=====================
*/
void monster_base::monster_base_lost_combat() {
entity node;
vector ang;
float dist;
float yaw;
float attack_flags;
entity possibleEnemy;
float allow_attack;
float lost_time;
lost_time = sys.getTime() + sys.getFrameTime();
allow_attack = sys.getTime() + 4;
node = getClosestHiddenTarget( "ai_lostcombat" );
if ( node ) {
dist = distanceTo( node );
if ( dist < 40 ) {
// fixes infinite loops when close to lost combat node
waitFrame();
} else {
run = true;
moveToEntity( node );
while( !AI_MOVE_DONE ) {
if ( sys.influenceActive() ) {
break;
}
possibleEnemy = heardSound( true );
if ( possibleEnemy ) {
if ( canReachEntity( possibleEnemy ) ) {
setEnemy( possibleEnemy );
break;
}
}
if ( canReachEnemy() ) {
if ( AI_ENEMY_IN_FOV || AI_PAIN ) {
break;
}
}
if ( check_blocked() ) {
break;
}
waitFrame();
// allow attacks when enemy is outside of fov
if ( sys.getTime() > allow_attack ) {
AI_ENEMY_IN_FOV = AI_ENEMY_VISIBLE;
}
attack_flags = check_attacks();
if ( attack_flags ) {
do_attack( attack_flags );
setState( "state_Combat" );
}
}
ang = node.getAngles();
turnTo( ang_y );
while( !AI_MOVE_DONE ) {
if ( canReachEnemy() ) {
if ( heardSound( true ) ) {
break;
}
if ( AI_ENEMY_IN_FOV || AI_PAIN ) {
break;
}
}
waitFrame();
}
}
} else {
run = true;
moveToCover();
if ( AI_DEST_UNREACHABLE ) {
combat_wander();
}
// if we're not already in cover
if ( !AI_MOVE_DONE ) {
while( !AI_MOVE_DONE ) {
if ( sys.influenceActive() ) {
break;
}
// allow attacks when enemy is outside of fov
if ( sys.getTime() > allow_attack ) {
AI_ENEMY_IN_FOV = AI_ENEMY_VISIBLE;
}
attack_flags = check_attacks();
if ( attack_flags ) {
do_attack( attack_flags );
setState( "state_Combat" );
}
possibleEnemy = heardSound( true );
if ( possibleEnemy ) {
if ( canReachEntity( possibleEnemy ) ) {
setEnemy( possibleEnemy );
break;
}
}
if ( canReachEnemy() ) {
if ( AI_ENEMY_IN_FOV || AI_PAIN ) {
break;
}
}
if ( check_blocked() ) {
break;
}
waitFrame();
}
if ( !sys.influenceActive() ) {
if ( AI_ENEMY_VISIBLE ) {
faceEnemy();
} else if ( AI_MOVE_DONE ) {
// turn around to face the way we came
yaw = getCurrentYaw();
turnTo( yaw + 180 );
}
}
}
}
// wait at least 1 frame to avoid loops
waitFrame();
if ( lost_time >= sys.getTime() ) {
combat_wander();
}
if ( AI_ENEMY_VISIBLE || sys.influenceActive() ) {
setState( "state_Combat" );
} else {
clearEnemy();
setState( "state_Idle" );
}
}
/*
=====================
monster_base::combat_wander
=====================
*/
void monster_base::combat_wander() {
float endtime;
float mintime;
mintime = sys.getTime() + 0.2;
endtime = sys.getTime() + 3;
wander();
while( sys.getTime() < endtime ) {
if ( sys.influenceActive() ) {
break;
}
if ( check_attacks() ) {
break;
}
if ( sys.getTime() > mintime ) {
if ( canReachEnemy() ) {
if ( heardSound( true ) ) {
break;
}
if ( AI_ENEMY_IN_FOV || AI_PAIN ) {
break;
}
}
}
waitFrame();
}
// wait at least 1 frame to avoid loops
waitFrame();
setState( "state_Combat" );
}
/*
=====================
monster_base::state_FollowAlternatePath
=====================
*/
void monster_base::state_FollowAlternatePath() {
if ( inAnimState( ANIMCHANNEL_TORSO, "Torso_CustomCycle" ) || inAnimState( ANIMCHANNEL_TORSO, "Torso_CustomAnim" ) ) {
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 8 );
}
ignoreEnemies = true;
idle_followPathEntities( current_path );
ignoreEnemies = false;
setState( "state_Idle" );
}
/*
=====================
monster_base::follow_alternate_path1
=====================
*/
void monster_base::follow_alternate_path1() {
current_path = getEntityKey( "alt_path1" );
setState( "state_FollowAlternatePath" );
}
/*
=====================
monster_base::follow_alternate_path2
=====================
*/
void monster_base::follow_alternate_path2() {
current_path = getEntityKey( "alt_path2" );
setState( "state_FollowAlternatePath" );
}
/*
=====================
monster_base::follow_alternate_path3
=====================
*/
void monster_base::follow_alternate_path3() {
current_path = getEntityKey( "alt_path2" );
setState( "state_FollowAlternatePath" );
}